package org.raymond.iworks.study.orders.service.impl;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.map.UnmodifiableMap;
import org.raymond.iworks.apis.common.ErrorCode;
import org.raymond.iworks.apis.exception.BaseException;
import org.raymond.iworks.repository.repository.TSysUserRepository;
import org.raymond.iworks.study.orders.common.GlobalCache;
import org.raymond.iworks.study.orders.common.RandomProduct;
import org.raymond.iworks.study.orders.entity.TInventories;
import org.raymond.iworks.study.orders.entity.TInventoriesSegments;
import org.raymond.iworks.study.orders.entity.TProducts;
import org.raymond.iworks.study.orders.repository.TInventoryRepository;
import org.raymond.iworks.study.orders.repository.TInventorySegmentsRepository;
import org.raymond.iworks.study.orders.repository.TProductRepository;
import org.raymond.iworks.study.orders.service.IInventoryService;
import org.raymond.iworks.study.orders.service.IOrderService;
import org.raymond.iworks.study.orders.service.ISecKillService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

@Slf4j
@Service
public class SecKillServiceImpl implements ISecKillService {
    @Autowired
    private TProductRepository tProductRepository;
    @Autowired
    private TInventoryRepository tInventoryRepository;
    @Autowired
    private TInventorySegmentsRepository tInventorySegmentsRepository;
    @Autowired
    private TSysUserRepository tSysUserRepository;
    @Autowired
    private IOrderService iOrderService;
    @Autowired
    private IInventoryService iInventoryService;


    @Override
    public void initData() {
        log.info("init data...");
        // 初始化随机用户 1万个
        /*
        List<TSysUser> tSysUserList = new ArrayList<>();
        for(int i=0; i<10000; i++) {
            tSysUserList.add(RandomUser.create("123456"));
        }
        log.info("remain: {}", tSysUserList.size());
        tSysUserRepository.saveAll(tSysUserList);
        log.info("batch saved user");
         */
        // 初始化随机商品 100个
        List<TProducts> tProductsList = new ArrayList<>();
        for(int i=0; i<100; i++) {
            tProductsList.add(RandomProduct.create());
        }
        tProductRepository.saveAll(tProductsList);
        log.info("batch saved product");
        // 商品库存默认10万个
        int invNum = 100_000;
        List<TInventories> tInventoriesList = tProductsList.stream().map(product->{
            TInventories inventories = TInventories.builder()
                    .productId(product.getId())
                    .invNum(invNum)
                    .usableNum(invNum)
                    .lockNum(0)
                    .build();
            return inventories;
        }).collect(Collectors.toList());
        tInventoryRepository.saveAll(tInventoriesList);
        log.info("batch saved inv");
    }

    private Map<Long, AtomicBoolean> initProductActivityMap = new ConcurrentHashMap<>();

    /**
     * 初始化秒杀活动
     */
    @Override
    public synchronized void initSecKillActivity() {
        List<TInventories> tInventoriesList = tInventoryRepository.findAll();
        if(tInventoriesList==null || tInventoriesList.isEmpty()){
            log.error("库存为空");
            return;
        }
        // 根据当前库存对秒杀产品的库存进行分段
        int coreProcessors = Runtime.getRuntime().availableProcessors();
        int threads = coreProcessors * 2 + 2; // 秒杀活动属于io密集型, 核心数+cpu等待时间/cpu运行时间
        Map<Long, Integer> rotateCountMap = new ConcurrentHashMap<>();
        Map<Long, Integer> maxMap = new ConcurrentHashMap<>();
        List<TInventoriesSegments> allSegmentsList = new ArrayList<>();

        for(TInventories inventory : tInventoriesList){
            Long productId = inventory.getProductId();
            rotateCountMap.put(productId, 0);
            Double invNumDouble = inventory.getInvNum()*1.1;
            maxMap.put(productId, invNumDouble.intValue());
            int usableNum = inventory.getUsableNum();
            if(usableNum<=0){
                log.warn("库存[{}]产品[{}]无可用库存", inventory.getId(), productId);
                continue;
            }
            int lockNum = inventory.getLockNum();
            int invNum = inventory.getInvNum();
            List<TInventoriesSegments> segmentsList = new ArrayList<>();
            String segmentLockFormat = "ProductLock:"+productId+"-";
            if(usableNum<=threads){
                // 如果可用库存小于等于线程数,则每个可用库存都可以由一个线程进行写操作
                for(int i=0; i<usableNum; i++){
                    TInventoriesSegments segments = TInventoriesSegments.builder()
                            .productId(productId)
                            .segmentInvNum(0)
                            .segmentUsableNum(1)
                            .segmentLockNum(0)
                            .segmentLock(segmentLockFormat+i)
                            .build();
                    segmentsList.add(segments);
                }
            }else {
                // 如果可用库存大于线程数,则每个线程分配一定的库存数以供秒杀活动的开展
                int segmentUsableNum = usableNum / threads;
                // 如果可用库存不能整除线程数,则多余的可用库存逐个分配到每个线程上
                int remainUsableNum = usableNum % threads;
                for(int i=0; i<threads; i++){
                    TInventoriesSegments segments = TInventoriesSegments.builder()
                            .productId(productId)
                            .segmentInvNum(0)
                            .segmentUsableNum(segmentUsableNum)
                            .segmentLockNum(0)
                            .segmentLock(segmentLockFormat+i)
                            .build();
                    segmentsList.add(segments);
                }
                for(int i=0; i<remainUsableNum; i++){
                    TInventoriesSegments segments = segmentsList.get(i);
                    segments.setSegmentUsableNum(segments.getSegmentUsableNum()+1);
                }
            }
            // 参与秒杀活动的商品可能已经销售过,为简单处理,其总库存与已锁单库存都放入第一个线程
            TInventoriesSegments firstSegment = segmentsList.get(0);
            firstSegment.setSegmentInvNum(invNum);
            firstSegment.setSegmentLockNum(lockNum);

            allSegmentsList.addAll(segmentsList);
        }
        tInventorySegmentsRepository.saveAllAndFlush(allSegmentsList);

        // 构建全局缓存
        Map<Long, List<String>> segmentProductLockMap = UnmodifiableMap.unmodifiableMap(
                allSegmentsList.stream().collect(
                        Collectors.groupingBy(TInventoriesSegments::getProductId,
                                Collectors.mapping(TInventoriesSegments::getSegmentLock, Collectors.toList()))));
        GlobalCache.injectSegmentlockMap(segmentProductLockMap);
        GlobalCache.injectRotateCountMap(rotateCountMap);
        GlobalCache.injectMaxMap(maxMap);
    }

    /**
     * 普通写: 10线程,每线程200个用户,固定一个商品,不写订单
     * 1, 样本:2000,平均:24,最小:0,最大:464,吞吐量:386.9/s,中位数:19,99分位:83,95分位:50 没预热
     * 2, 样本:2000,平均:21,最小:0,最大:535,吞吐量:404/s,中位数:18,99分位:64,95分位:39 没预热
     * 3, 样本:2000,平均:21,最小:0,最大:507,吞吐量:405.7/s,中位数:18,99分位:59,95分位:41 没预热
     * 分段写: 10线程,每线程200个用户,固定一个商品,不写订单,添加普通索引product_id
     * 1, 样本:2000,平均:12,最小:0,最大:418,吞吐量:619/s,中位数:9,99分位:58,95分位:26 没预热
     * 2, 样本:2000,平均:9,最小:0,最大:95,吞吐量:765.1/s,中位数:7,99分位:34,95分位:21 接1 clear直接run
     * 3, 样本:2000,平均:8,最小:0,最大:61,吞吐量:822/s,中位数:6,99分位:44,95分位:21 接2 clear直接run
     * 分段写: 10线程,每线程200个用户,固定一个商品,不写订单,索引改为产品Id+分段锁字段
     * 1, 样本:2000,平均:11,最小:0,最大:338,吞吐量:674.8/s,中位数:9,99分位:50,95分位:21 没预热
     * 2, 样本:2000,平均:7,最小:0,最大:60,吞吐量:767.2/s,中位数:9,99分位:41,95分位:24 接1 clear直接run
     * 3, 样本:2000,平均:5,最小:0,最大:33,吞吐量:1114.2/s,中位数:5,99分位:15,95分位:8 接1 clear直接run
     * 分段写: 32线程,每线程200用户,先用一个商品刷两次(640,720),再用另一个商品刷两次(930,1033),不写订单,商品id作为索引
     * 1, 样本:6400,平均:26,最小:0,最大:68,吞吐量:1033.3/s,中位数:27,99分位:41,95分位:36
     * 分段写: 32线程,每线程200用户,固定一个山坡,不写订单,商品id+分段锁字段作为索引
     * 1, 样本:6400,平均:25,最小:0,最大:475,吞吐量:1107.8/s,中位数:21,99分位:81,95分位:49
     * 登记insert: 10线程,每线程200个用户,固定一个商品,不写订单
     * 1, 样本:2000,平均:11,最小:0,最大:378,吞吐量:647.7/s,中位数:10,99分位:35,95分位:22 没预热
     * 2, 样本:2000,平均:13,最小:0,最大:356,吞吐量:575.9/s,中位数:9,99分位:62,95分位:33 没预热
     * 3, 样本:2000,平均:13,最小:0,最大:364,吞吐量:572.9/s,中位数:12,99分位:40,95分位:26 没预热
     * 登记到内存队列,通过定时任务批量写入: 10线程,每线程200个用户,固定一个商品,不写订单
     * 1, 样本:2000,平均:2,最小:0,最大:321,吞吐量:1549.2/s,中位数:2,99分位:5,95分位:3 没预热
     * 2, 样本:2000,平均:2,最小:0,最大:301,吞吐量:1588.6/s,中位数:2,99分位:6,95分位:3 没预热
     * 登记到内存队列,通过定时任务批量写入: 32线程,每线程200个用户,固定一个商品,不写订单
     * 1, 样本:6400,平均:7,最小:0,最大:500,吞吐量:2495.1/s,中位数:2,99分位:160,95分位:11 没预热
     *
     */
    @Override
    public void secKill(Long userId, Long productId, Integer num) {
        if(num<=0) throw new BaseException(ErrorCode.BAD_REQUEST, "订单商品数量不能小于等于0");
        // iOrderService.normalImplSecKill(userId, productId, num);

        // iInventoryService.lockInv(productId, num);
        // iOrderService.secKillOrder(userId, productId, num);

        iInventoryService.invReg(userId, productId);
    }

    @Override
    public void cancel(Long userId, Long productId) {
        iOrderService.normalCancel(userId, productId);
    }
}
